#include <algorithm>
#include <chrono>
#include <condition_variable>
#include <ctime>
#include <getopt.h>
#include <glib.h>
#include <glib-unix.h>
#include <gio/gio.h>
#include <gio/gunixinputstream.h>
#include <mutex>
#include <nice/debug.h>
#include <sstream>
#include <set>
#include <thread>
#include <xdtls_srtp.h>
#include <xm_log.h>

#include "codecs.hpp"
#include "child.hpp"
#include "master.hpp"

/// Media Constants
static constexpr auto kTickIntervalMs = std::chrono::milliseconds(10);
static constexpr auto kStatIntervalMs = std::chrono::milliseconds(100);

/// Options
enum Option {
    OptionHelp,
    OptionVerbose,
    OptionLogConfig,
    OptionServer,
    OptionWorker,
    OptionConcurrency,
    OptionRampUp,
    OptionDuration,
    OptionMediaAddress,
    OptionVideoBitrate,
    OptionAnswerer,
    OptionReplaceCandidate,
    OptionAudio,
    OptionDebug,
    OptionMixerId,
    OptionSubscribe,
    OptionChild,
    OptionMetricServer,
    OptionMinPort,
    OptionMaxPort,
    OptionPubs,
    OptionPubRtc,
};

static const char* shortOptions = "";

static const struct option longOptions[] = {
        {   "help",                 no_argument,        nullptr,    OptionHelp              },
        {   "verbose",              no_argument,        nullptr,    OptionVerbose           },
        {   "log-config",           required_argument,  nullptr,    OptionLogConfig         },
        {   "rtc-server",           required_argument,  nullptr,    OptionServer            },
        {   "worker",               required_argument,  nullptr,    OptionWorker            },
        {   "concurrency",          required_argument,  nullptr,    OptionConcurrency       },
        {   "ramp-up",              required_argument,  nullptr,    OptionRampUp            },
        {   "duration",             required_argument,  nullptr,    OptionDuration          },
        {   "media-address",        required_argument,  nullptr,    OptionMediaAddress      },
        {   "video-bitrate",        required_argument,  nullptr,    OptionVideoBitrate      },
        {   "answer",               no_argument,        nullptr,    OptionAnswerer          },
        {   "replace-candidate",    required_argument,  nullptr,    OptionReplaceCandidate  },
        {   "audio",                required_argument,  nullptr,    OptionAudio             },
        {   "debug",                required_argument,  nullptr,    OptionDebug             },
        {   "mixer-id",             required_argument,  nullptr,    OptionMixerId           },
        {   "subscribe",            no_argument,        nullptr,    OptionSubscribe         },
        {   "child",                no_argument,        nullptr,    OptionChild             },
        {   "metric-server",        required_argument,  nullptr,    OptionMetricServer      },
        {   "min-port",             required_argument,  nullptr,    OptionMinPort           },
        {   "max-port",             required_argument,  nullptr,    OptionMaxPort           },
        {   "pubs",                 required_argument,  nullptr,    OptionPubs              },
        {   "pub-rtc",              required_argument,  nullptr,    OptionPubRtc            },
        {   0,                      0,                  0,          0                       },
};

uint32_t parseBitrate(char* str) {
    char* unit = &str[strlen(str) - 1];
    int ratio = 1;
    switch (*unit) {
        case 'k':
            ratio = 1000;
            *unit = '\0';
            break;
        case 'K':
            ratio = 1024;
            *unit = '\0';
            break;
        case 'm':
            ratio = 1000000;
            *unit = '\0';
            break;
        case 'M':
            ratio = 1024 * 1024;
            *unit = '\0';
            break;
    }
    return atoi(str) * ratio;
}

/// Functions
void printUsage(const char* progName) {
    fprintf(stderr, "Usage: %s [options]\n", progName);
    fprintf(stderr, "Options:\n");
    fprintf(stderr, "  -h,--help                             print this\n");
    fprintf(stderr, "  -v,--verbose                          print more info\n");
    fprintf(stderr, "  -l,--log-config-file <file>           log4cplus config file\n");
    fprintf(stderr, "  -s,--rtc-server      <server>         hostname or ip address\n");
    fprintf(stderr, "  -w,--worker          <worker>         how many workers\n");
    fprintf(stderr, "  -c,--concurrency     <concurrency>    how many connections\n");
    fprintf(stderr, "  -u,--ramp-up         <seconds>        ramp up seconds\n");
    fprintf(stderr, "  -d,--duration        <seconds>        total run duration\n");
    fprintf(stderr, "  -m,--media-address   <media address>  local media address\n");
    fprintf(stderr, "  -n,--media-port-min  <port>           min local port media used\n");
    fprintf(stderr, "  -n,--media-port-max  <port>           max local port media used\n");
    fprintf(stderr, "  -V,--video-bitrate   <bitrate>        video bitrate\n");
    fprintf(stderr, "  -a,--answer                           works as answerer, by default works as offerer\n");
    fprintf(stderr, "  -R,--replace-candidate <from-to>      replace remote candidate to rtc server address, useful if rtc-xswitch behind NAT\n");
    fprintf(stderr, "  -A,--audio           <audio-file>     audio file to play, each audio file is played to one connection\n");
    fprintf(stderr, "  -D,--debug           <mod>            enable debug module\n");
    fprintf(stderr, "  -S,--subscribe                        subscribe self\n");
    fprintf(stderr, "  --child                               \n");
    fprintf(stderr, "  --metric-server      <ip:port>        metric server ip and port\n");
    fprintf(stderr, "  --min-port           <port>           min udp port to use\n");
    fprintf(stderr, "  --max-port           <port>           max udp port to use\n");
    fprintf(stderr, "  --pubs               <num>            number of publisher\n");
    fprintf(stderr, "  --pub-rtc            <rtc-id>         publisher rtc id\n");
}

void parse_options(Context &bc, int argc, char* argv[]) {
    int c;
    while ((c = getopt_long(argc, argv, shortOptions, longOptions, nullptr)) != -1) {
        switch (c) {
            case OptionHelp:
                printUsage(argv[0]);
                exit(-1);
            case OptionVerbose:
                ++bc.verbosity;
                break;
            case OptionLogConfig:
                bc.logConfigFile = optarg;
                break;
            case OptionServer:
                bc.rtcAddresses.emplace_back(std::string(optarg));
                break;
            case OptionWorker:
                bc.workers = atoi(optarg);
                break;
            case OptionConcurrency:
                bc.concurrency = atoi(optarg);
                break;
            case OptionRampUp:
                bc.rampUpSeconds = atoi(optarg);
                break;
            case OptionDuration:
                bc.durationSeconds = atoi(optarg);
                break;
            case OptionMediaAddress:
                bc.mediaAddress = optarg;
                break;
            case OptionVideoBitrate:
                bc.video_bitrate = parseBitrate(optarg);
                break;
            case OptionAnswerer:
                bc.sequence = Context::Sequence::Answerer;
                break;
            case OptionReplaceCandidate:
            {
                auto from_to = std::string(optarg);
                auto sep = from_to.find('-');
                if (sep == std::string::npos) {
                    fprintf(stderr, "invalid option:\n");
                    fprintf(stderr, "--replace-candidate from-to\n");
                    exit(-1);
                }
                bc.replace_candidate = true;
                bc.replace_candidate_from = from_to.substr(0, sep);
                bc.replace_candidate_to = from_to.substr(sep+1);
                fprintf(stderr, "candidate will be replaced from %s to %s\n", bc.replace_candidate_from.c_str(), bc.replace_candidate_to.c_str());
            }
                break;
            case OptionAudio:
            {
                bc.audio_files.emplace_back(optarg);
//                auto audio = new Audio();
//                int ret = audio->LoadFile(fn);
//                if (ret != 0) {
//                    fprintf(stderr, "Could not load audio file %s", fn.c_str());
//                    exit(-1);
//                }
//                bc.audios.emplace_back(audio);
                break;
            }
            case OptionMixerId:
                bc.mixer_id = optarg;
                break;
            case OptionSubscribe:
                bc.subscribe_self = true;
                break;
            case OptionChild:
                bc.child = true;
                break;
            case OptionMetricServer:
                bc.metric_server_address = optarg;
                break;
            case OptionMinPort:
                bc.minMediaPort = atoi(optarg);
                break;
            case OptionMaxPort:
                bc.maxMediaPort = atoi(optarg);
                break;
            case OptionPubs:
                bc.n_pub = atoi(optarg);
                break;
            case OptionPubRtc:
                bc.pub_rtc_ids.emplace_back(optarg);
                break;
            case '?':
                if (optopt == 's' || optopt=='p' || optopt == 'c' || optopt == 'u' || optopt == 'd' || optopt == 'm'){
                    fprintf (stderr, "Option -%c requires an argument.\n", optopt);
                } else if (isprint (optopt)){
                    fprintf (stderr, "Unknown option '%s'.\n", argv[optind-1]);
                } else{
                    fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt);
                }
                printUsage(argv[0]);
                exit(-1);
            default:
                fprintf (stderr, "Unknown option '%s'.\n", argv[optind-1]);
                printUsage(argv[0]);
                exit(-1);
        }
    }
}

Context ctx;

int main(int argc, char* argv[]) {
    // nice debug cost lots of cpu
    nice_debug_disable(TRUE);

    parse_options(ctx, argc, argv);

    auto n_port = ctx.maxMediaPort - ctx.minMediaPort + 1;
    auto least_port = ctx.concurrency * ctx.workers * 4;
    if (n_port < least_port) {
        g_warning("more ports needed, at least %d", least_port);
        return -1;
    }

    xm_log_init(ctx.logConfigFile);

    g_log_set_handler(nullptr, G_LOG_LEVEL_DEBUG, g_log_default_handler, NULL);

    dbgi("bench args:\n %s", ctx.toString().c_str());

    int error = 0;
    if ((error = xdtls_srtp_init(nullptr, nullptr))) {
        g_warning("init dtls module failed, error %d", error);
        exit(-1);
    }

    rtc_codec_init(false, false);

    if (ctx.child) {
        auto it = ctx.audio_files.begin();
        while (it != ctx.audio_files.end()) {
            char* fn = *it;
            auto audio = new Audio();
            int ret = audio->LoadFile(fn);
            if (ret != 0) {
                g_warning("load audio file %s error: %d", fn, ret);
                exit(-1);
            }
            ctx.audios.emplace_back(audio);
            it++;
        }

        child_main(ctx);
    } else {
        master_main(ctx, argc, argv);
    }
}
